package com.czbspringmvc.context;

import com.czbspringmvc.annotation.AutoWired;
import com.czbspringmvc.annotation.Controller;
import com.czbspringmvc.annotation.Service;
import com.czbspringmvc.xml.XMLParser;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: 陈正彬
 * 自己的Spring容器
 */
public class CzbWebApplicationContext {
    //定义属性classFullPathList，保存扫描包/子包的类的全路径
    private List<String> classFullPathList = new ArrayList<>();

    //存放反射生成的Bean对象,例如：/Controller/Service
    public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();

    public CzbWebApplicationContext() {//无参构造器
    }

    private String configLocation; //表示spring容器的配置文件

    public CzbWebApplicationContext(String configLocation) {//有参构造器
        this.configLocation = configLocation;
    }

    //编写方法完成自己spring容器的初始化
    public void init() {
        //String basePackage = XMLParser.getBasePackage("czbspringmvc.xml");
        String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
        String[] basePackages = basePackage.split(",");
        //遍历basePackages，完成包的扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);
            }
        }

        System.out.println("classFullPathList = " + classFullPathList);

        //将扫描到的类，反射生成对象，并保存到ioc容器中
        executeInstance();
        System.out.println("扫描后ioc = " + ioc);

        //完成注入的bean对象的属性的装配
        executeAutoWired();
        System.out.println("装配后ioc = " + ioc);
    }

    //创建方法，完成对包的扫描
    public void scanPackage(String pack) {

        //得到包所在的工作路径
        //注意：不要用Junit测试，否则 url 为空; 要启动tomcat测试
        URL url =
                this.getClass().getClassLoader()
                        .getResource("/" + pack.replaceAll("\\.", "/"));
//        System.out.println("url = " + url);

        //根据得到的路径，对其进行扫描，把类的全路径保存到classFullPathList中
        String path = url.getFile();
        File dir = new File(path);
        for (File file : dir.listFiles()) {
            //判断是否是目录
            if (file.isDirectory()) {
                //如果是目录，则递归调用
                scanPackage(pack + "." + file.getName());
            } else {
                //不是目录，则添加到classFullPathList中
                String classFullPath = pack + "." + file.getName().replaceAll(".class", "");
                classFullPathList.add(classFullPath);
            }
        }

    }

    //创建方法，将扫描到的类，进行反射生成对象，并保存到ioc容器中
    public void executeInstance() {
        //判断是否扫描到类
        if (classFullPathList.size() == 0) {//没有
            return;
        }
        try {
            for (String classFullPath : classFullPathList) {
                Class<?> aClass = Class.forName(classFullPath);
                //判断是否有@Controller
                if (aClass.isAnnotationPresent(Controller.class)) {
                    Object o = aClass.newInstance();
                    //获取类名首字母小写，作为key
                    String className = aClass.getSimpleName().substring(0, 1).toLowerCase() +
                            aClass.getSimpleName().substring(1);
                    //将类名作为key，对象作为value，保存到ioc容器中
                    ioc.put(className, o);
                } //如果有其他注解在 继续完善, 处理service
                else if (aClass.isAnnotationPresent(Service.class)) {

                    //获取service的value
                    Service serviceAnnotation = aClass.getAnnotation(Service.class);
                    String beanName = serviceAnnotation.value();
                    if ("".equals(beanName)) {
                        //说明注解没有指定Value值，使用默认机制注入Service
                        //通过接口/类 名(首字母小写)来注入ioc容器

                        //得到所有接口
                        Class<?>[] interfaces = aClass.getInterfaces();
                        Object instance = aClass.newInstance();
                        for (Class<?> anInterface : interfaces) {
                            String beanName2 = anInterface.getSimpleName().substring(0, 1).toLowerCase() +
                                    anInterface.getSimpleName().substring(1);
                            ioc.put(beanName2, instance);
                        }

                    } else {//如果有指定名字，就直接注入
                        ioc.put(beanName, aClass.newInstance());
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //编写方法，完成属性的自动装配
    public void executeAutoWired() {
        //判断ioc是否有要装配的对象
        if (ioc.isEmpty()) {
            return;
        }
        //遍历ioc容器中所有注入的bean对象，然后获取到bean的所有字段/属性，判断是否需要装配
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            String key = entry.getKey();
            Object bean = entry.getValue();

            //得到bean的所有属性/字段
            Field[] declaredFields = bean.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判断字段是否有@AutoWired
                if (declaredField.isAnnotationPresent(AutoWired.class)) {//有
                    AutoWired autoWiredAnnotation = declaredField.getAnnotation(AutoWired.class);
                    String beanName = autoWiredAnnotation.value();
                    //判断value是否为空
                    if ("".equals(beanName)) {
                        //没有设置value，按照默认匹配(字段类型名称首字母小写)
                        Class<?> type = declaredField.getType();
                        beanName = type.getSimpleName().substring(0, 1).toLowerCase() +
                                type.getSimpleName().substring(1);

                    }
                    //设置了value，就按照beanName装配
                    //从ioc容器中获取bean
                    if (null == ioc.get(beanName)) {
                        throw new RuntimeException("ioc容器中，不存在你要装配的bean");
                    }
                    //防止属性为private，选择暴力破解
                    declaredField.setAccessible(true);
                    //装配属性
                    try {
                        declaredField.set(bean, ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }

                }
            }
        }
    }
}
